{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "1、安装依赖。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: langchain in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (0.3.7)\n",
      "Requirement already satisfied: langchain-openai in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (0.2.6)\n",
      "Collecting langchain-openai\n",
      "  Downloading langchain_openai-0.2.8-py3-none-any.whl.metadata (2.6 kB)\n",
      "Requirement already satisfied: langgraph in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (0.2.45)\n",
      "Collecting langgraph\n",
      "  Downloading langgraph-0.2.50-py3-none-any.whl.metadata (15 kB)\n",
      "Requirement already satisfied: langsmith in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (0.1.142)\n",
      "Collecting langsmith\n",
      "  Downloading langsmith-0.1.143-py3-none-any.whl.metadata (13 kB)\n",
      "Requirement already satisfied: PyYAML>=5.3 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain) (6.0.2)\n",
      "Requirement already satisfied: SQLAlchemy<3,>=1.4 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain) (2.0.35)\n",
      "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain) (3.10.10)\n",
      "Requirement already satisfied: langchain-core<0.4.0,>=0.3.15 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain) (0.3.15)\n",
      "Requirement already satisfied: langchain-text-splitters<0.4.0,>=0.3.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain) (0.3.2)\n",
      "Requirement already satisfied: numpy<2.0.0,>=1.26.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain) (1.26.4)\n",
      "Requirement already satisfied: pydantic<3.0.0,>=2.7.4 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain) (2.9.2)\n",
      "Requirement already satisfied: requests<3,>=2 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain) (2.32.3)\n",
      "Requirement already satisfied: tenacity!=8.4.0,<10,>=8.1.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain) (8.2.3)\n",
      "Collecting langchain-core<0.4.0,>=0.3.15 (from langchain)\n",
      "  Downloading langchain_core-0.3.19-py3-none-any.whl.metadata (6.3 kB)\n",
      "Requirement already satisfied: openai<2.0.0,>=1.54.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain-openai) (1.54.3)\n",
      "Requirement already satisfied: tiktoken<1,>=0.7 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain-openai) (0.8.0)\n",
      "Collecting langgraph-checkpoint<3.0.0,>=2.0.4 (from langgraph)\n",
      "  Downloading langgraph_checkpoint-2.0.4-py3-none-any.whl.metadata (4.6 kB)\n",
      "Requirement already satisfied: langgraph-sdk<0.2.0,>=0.1.32 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langgraph) (0.1.35)\n",
      "Requirement already satisfied: httpx<1,>=0.23.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langsmith) (0.27.2)\n",
      "Requirement already satisfied: orjson<4.0.0,>=3.9.14 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langsmith) (3.10.11)\n",
      "Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langsmith) (1.0.0)\n",
      "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (2.4.3)\n",
      "Requirement already satisfied: aiosignal>=1.1.2 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.3.1)\n",
      "Requirement already satisfied: attrs>=17.3.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (24.2.0)\n",
      "Requirement already satisfied: frozenlist>=1.1.1 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.5.0)\n",
      "Requirement already satisfied: multidict<7.0,>=4.5 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (6.1.0)\n",
      "Requirement already satisfied: yarl<2.0,>=1.12.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.17.1)\n",
      "Requirement already satisfied: anyio in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from httpx<1,>=0.23.0->langsmith) (4.6.2.post1)\n",
      "Requirement already satisfied: certifi in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from httpx<1,>=0.23.0->langsmith) (2024.8.30)\n",
      "Requirement already satisfied: httpcore==1.* in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from httpx<1,>=0.23.0->langsmith) (1.0.6)\n",
      "Requirement already satisfied: idna in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from httpx<1,>=0.23.0->langsmith) (3.10)\n",
      "Requirement already satisfied: sniffio in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from httpx<1,>=0.23.0->langsmith) (1.3.1)\n",
      "Requirement already satisfied: h11<0.15,>=0.13 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from httpcore==1.*->httpx<1,>=0.23.0->langsmith) (0.14.0)\n",
      "Requirement already satisfied: jsonpatch<2.0,>=1.33 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain-core<0.4.0,>=0.3.15->langchain) (1.33)\n",
      "Requirement already satisfied: packaging<25,>=23.2 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain-core<0.4.0,>=0.3.15->langchain) (24.1)\n",
      "Requirement already satisfied: typing-extensions>=4.7 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langchain-core<0.4.0,>=0.3.15->langchain) (4.12.2)\n",
      "Requirement already satisfied: msgpack<2.0.0,>=1.1.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langgraph-checkpoint<3.0.0,>=2.0.4->langgraph) (1.1.0)\n",
      "Requirement already satisfied: httpx-sse>=0.4.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from langgraph-sdk<0.2.0,>=0.1.32->langgraph) (0.4.0)\n",
      "Requirement already satisfied: distro<2,>=1.7.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from openai<2.0.0,>=1.54.0->langchain-openai) (1.9.0)\n",
      "Requirement already satisfied: jiter<1,>=0.4.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from openai<2.0.0,>=1.54.0->langchain-openai) (0.7.0)\n",
      "Requirement already satisfied: tqdm>4 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from openai<2.0.0,>=1.54.0->langchain-openai) (4.67.0)\n",
      "Requirement already satisfied: annotated-types>=0.6.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from pydantic<3.0.0,>=2.7.4->langchain) (0.7.0)\n",
      "Requirement already satisfied: pydantic-core==2.23.4 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from pydantic<3.0.0,>=2.7.4->langchain) (2.23.4)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from requests<3,>=2->langchain) (3.4.0)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from requests<3,>=2->langchain) (2.2.3)\n",
      "Requirement already satisfied: greenlet!=0.4.17 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from SQLAlchemy<3,>=1.4->langchain) (3.1.1)\n",
      "Requirement already satisfied: regex>=2022.1.18 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from tiktoken<1,>=0.7->langchain-openai) (2024.11.6)\n",
      "Requirement already satisfied: jsonpointer>=1.9 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.4.0,>=0.3.15->langchain) (3.0.0)\n",
      "Requirement already satisfied: colorama in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from tqdm>4->openai<2.0.0,>=1.54.0->langchain-openai) (0.4.6)\n",
      "Requirement already satisfied: propcache>=0.2.0 in d:\\anaconda3\\envs\\fay312-2\\lib\\site-packages (from yarl<2.0,>=1.12.0->aiohttp<4.0.0,>=3.8.3->langchain) (0.2.0)\n",
      "Downloading langchain_openai-0.2.8-py3-none-any.whl (50 kB)\n",
      "Downloading langgraph-0.2.50-py3-none-any.whl (124 kB)\n",
      "Downloading langsmith-0.1.143-py3-none-any.whl (306 kB)\n",
      "Downloading langchain_core-0.3.19-py3-none-any.whl (409 kB)\n",
      "Downloading langgraph_checkpoint-2.0.4-py3-none-any.whl (23 kB)\n",
      "Installing collected packages: langsmith, langchain-core, langgraph-checkpoint, langchain-openai, langgraph\n",
      "  Attempting uninstall: langsmith\n",
      "    Found existing installation: langsmith 0.1.142\n",
      "    Uninstalling langsmith-0.1.142:\n",
      "      Successfully uninstalled langsmith-0.1.142\n",
      "  Attempting uninstall: langchain-core\n",
      "    Found existing installation: langchain-core 0.3.15\n",
      "    Uninstalling langchain-core-0.3.15:\n",
      "      Successfully uninstalled langchain-core-0.3.15\n",
      "  Attempting uninstall: langgraph-checkpoint\n",
      "    Found existing installation: langgraph-checkpoint 2.0.2\n",
      "    Uninstalling langgraph-checkpoint-2.0.2:\n",
      "      Successfully uninstalled langgraph-checkpoint-2.0.2\n",
      "  Attempting uninstall: langchain-openai\n",
      "    Found existing installation: langchain-openai 0.2.6\n",
      "    Uninstalling langchain-openai-0.2.6:\n",
      "      Successfully uninstalled langchain-openai-0.2.6\n",
      "  Attempting uninstall: langgraph\n",
      "    Found existing installation: langgraph 0.2.45\n",
      "    Uninstalling langgraph-0.2.45:\n",
      "      Successfully uninstalled langgraph-0.2.45\n",
      "Successfully installed langchain-core-0.3.19 langchain-openai-0.2.8 langgraph-0.2.50 langgraph-checkpoint-2.0.4 langsmith-0.1.143\n"
     ]
    }
   ],
   "source": [
    "!pip install -U langchain langchain-openai langgraph langsmith"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2、配置环境变量。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
    "os.environ[\"LANGCHAIN_ENDPOINT\"] = \"https://api.smith.langchain.com\"\n",
    "os.environ[\"LANGCHAIN_API_KEY\"] = \"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\"\n",
    "os.environ[\"LANGCHAIN_PROJECT\"] = \"rewoo\"\n",
    "\n",
    "os.environ[\"OPENAI_API_KEY\"] = \n",
    "os.environ[\"OPENAI_API_BASE\"] = "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "3、定义llm model。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Hello! How can I assist you today?'"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "llm = ChatOpenAI(model=\"gpt-4\")\n",
    "llm.invoke(\"Hello, world!\").content#test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "4、定义工具。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "现在时间是：23:54 星期二 2024年12月03日\n"
     ]
    }
   ],
   "source": [
    "import abc\n",
    "from typing import Any\n",
    "from datetime import datetime\n",
    "from langchain.tools import BaseTool\n",
    "\n",
    "class QueryTime(BaseTool, abc.ABC):\n",
    "    name: str = \"QueryTime\"\n",
    "    description: str = \"用于查询当前日期、星期几及时间\" \n",
    "\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "\n",
    "    async def _arun(self, *args: Any, **kwargs: Any) -> Any:\n",
    "        # 用例中没有用到 arun，不予具体实现\n",
    "        pass\n",
    "\n",
    "    def _run(self, para) -> str:\n",
    "        # 获取当前时间\n",
    "        now = datetime.now()\n",
    "        # 获取当前日期\n",
    "        today = now.date()\n",
    "        # 获取星期几的信息\n",
    "        week_day = today.strftime(\"%A\")\n",
    "        # 将星期几的英文名称转换为中文\n",
    "        week_day_zh = {\n",
    "            \"Monday\": \"星期一\",\n",
    "            \"Tuesday\": \"星期二\",\n",
    "            \"Wednesday\": \"星期三\",\n",
    "            \"Thursday\": \"星期四\",\n",
    "            \"Friday\": \"星期五\",\n",
    "            \"Saturday\": \"星期六\",\n",
    "            \"Sunday\": \"星期日\",\n",
    "        }.get(week_day, \"未知\")\n",
    "        # 将日期格式化为字符串\n",
    "        date_str = today.strftime(\"%Y年%m月%d日\")\n",
    "        # 将时间格式化为字符串\n",
    "        time_str = now.strftime(\"%H:%M\")\n",
    "\n",
    "        return f\"现在时间是：{time_str} {week_day_zh} {date_str}\"\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    tool = QueryTime()\n",
    "    result = tool.run(\"\")\n",
    "    print(result)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "天气预报信息：今天天气：clear sky，气温：23.19摄氏度，风速：1.74 m/s。\n"
     ]
    }
   ],
   "source": [
    "from typing import Any\n",
    "\n",
    "import requests\n",
    "from langchain.tools import BaseTool\n",
    "from urllib.parse import quote\n",
    "\n",
    "class Weather(BaseTool):\n",
    "    name: str = \"Weather\"\n",
    "    description: str = \"此工具用于获取天气预报信息，需传入英文的城市名，参数格式：Guangzhou\"\n",
    "\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "\n",
    "    async def _arun(self, *args: Any, **kwargs: Any) -> Any:\n",
    "        # 用例中没有用到 arun 不予具体实现\n",
    "        pass\n",
    "\n",
    "\n",
    "    def _run(self, para: str) -> str:\n",
    "        try:\n",
    "            if not para:\n",
    "                return \"参数不能为空\"\n",
    "            encoded_city = quote(para)\n",
    "\n",
    "            api_url = f\"http://api.openweathermap.org/data/2.5/weather?q={encoded_city}&appid=272fcb70d2c4e6f5134c2dce7d091df6\"\n",
    "            response = requests.get(api_url)\n",
    "            if response.status_code == 200:\n",
    "                weather_data = response.json()\n",
    "              # 提取天气信息\n",
    "                temperature_kelvin = weather_data['main']['temp']\n",
    "                temperature_celsius = temperature_kelvin - 273.15\n",
    "                min_temperature_kelvin = weather_data['main']['temp_min']\n",
    "                max_temperature_kelvin = weather_data['main']['temp_max']\n",
    "                min_temperature_celsius = min_temperature_kelvin - 273.15\n",
    "                max_temperature_celsius = max_temperature_kelvin - 273.15\n",
    "                description = weather_data['weather'][0]['description']\n",
    "                wind_speed = weather_data['wind']['speed']\n",
    "\n",
    "                # 构建天气描述\n",
    "                weather_description = f\"今天天气：{description}，气温：{temperature_celsius:.2f}摄氏度，风速：{wind_speed} m/s。\"\n",
    "\n",
    "                return f\"天气预报信息：{weather_description}\"\n",
    "            else:\n",
    "                return f\"无法获取天气预报信息，状态码：{response.status_code}\"\n",
    "        except Exception as e:\n",
    "            return f\"发生错误：{str(e)}\"\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    weather_tool = Weather()\n",
    "    weather_info = weather_tool.run(\"Guangzhou\")\n",
    "    print(weather_info)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "quertTime = QueryTime()\n",
    "weather = Weather()\n",
    "tools = [quertTime, weather]\n",
    "toolsStr = \"\"\n",
    "for tool in tools:\n",
    "    toolsStr += f\"{tool.name}[input]:{tool.description}\\n\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "5、定义状态"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List\n",
    "from typing_extensions import TypedDict\n",
    "from langgraph.graph import MessagesState\n",
    "\n",
    "class ReWOO(MessagesState):\n",
    "    task: str\n",
    "    plan_string: str\n",
    "    steps: List\n",
    "    results: dict\n",
    "    result: str\n",
    "    summary: str"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "6、定义Planner 节点"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt = \"\"\"For the following task, make plans that can solve the problem step by step. For each plan, indicate \\\n",
    "which external tool together with tool input to retrieve evidence. You can store the evidence into a \\\n",
    "variable #E that can be called by later tools. (Plan, #E1, Plan, #E2, Plan, ...)\n",
    "\n",
    "Tools can be one of the following:\n",
    "(1) LLM[input]: A pretrained LLM like yourself. Useful when you need to act with general\n",
    "world knowledge and common sense. Prioritize it when you are confident in solving the problem\n",
    "yourself. Input can be any instruction.\n",
    "{tools}\n",
    "\n",
    "For example,\n",
    "Task: Thomas, Toby, and Rebecca worked a total of 157 hours in one week. Thomas worked x\n",
    "hours. Toby worked 10 hours less than twice what Thomas worked, and Rebecca worked 8 hours\n",
    "less than Toby. How many hours did Rebecca work?\n",
    "Plan: Given Thomas worked x hours, translate the problem into algebraic expressions and solve\n",
    "with Wolfram Alpha. #E1 = WolframAlpha[Solve x + (2x − 10) + ((2x − 10) − 8) = 157]\n",
    "Plan: Find out the number of hours Thomas worked. #E2 = LLM[What is x, given #E1]\n",
    "Plan: Calculate the number of hours Rebecca worked. #E3 = Calculator[(2 ∗ #E2 − 10) − 8]\n",
    "\n",
    "Begin! \n",
    "Describe your plans with rich details. Each Plan should be followed by only one #E.\n",
    "\n",
    "Task: {task}\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "from langchain_core.messages import HumanMessage, SystemMessage\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "\n",
    "def get_plan(state: ReWOO):\n",
    "    regex_pattern = r\"Plan:\\s*(.+)\\s*(#E\\d+)\\s*=\\s*(\\w+)\\s*\\[([^\\]]+)\\]\"\n",
    "    prompt_template = ChatPromptTemplate.from_messages([SystemMessage(\"你的名字叫张三，18岁。\"), HumanMessage(content=prompt, name=\"User\")])#TODO 这里补充历史消息记录及总结\n",
    "    planner = prompt_template | llm\n",
    "    task = state[\"task\"]\n",
    "    result = planner.invoke({\"task\": task, \"tools\" : toolsStr})\n",
    "    # Find all matches in the sample text\n",
    "    matches = re.findall(regex_pattern, result.content)\n",
    "    return {\"steps\": matches, \"plan_string\": result.content}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "7、定义工具执行节点"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "def _get_current_task(state: ReWOO):\n",
    "    if \"results\" not in state or state[\"results\"] is None:\n",
    "        return 1\n",
    "    if len(state[\"results\"]) == len(state[\"steps\"]):\n",
    "        return None\n",
    "    else:\n",
    "        return len(state[\"results\"]) + 1\n",
    "\n",
    "\n",
    "def tool_execution(state: ReWOO):\n",
    "    \"\"\"Worker node that executes the tools of a given plan.\"\"\"\n",
    "    _step = _get_current_task(state)\n",
    "    _, step_name, tool, tool_input = state[\"steps\"][_step - 1]\n",
    "    _results = (state[\"results\"] or {}) if \"results\" in state else {}\n",
    "    for k, v in _results.items():\n",
    "        tool_input = tool_input.replace(k, v)\n",
    "    if tool == \"QueryTime\":\n",
    "        result = quertTime.invoke(tool_input)\n",
    "    elif tool == \"Weather\":\n",
    "        result = weather.invoke(tool_input)\n",
    "    elif tool == \"LLM\":\n",
    "        result = llm.invoke(tool_input)\n",
    "    else:\n",
    "        raise ValueError\n",
    "    _results[step_name] = str(result)\n",
    "    return {\"results\": _results}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "8、定义求解节点"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "solve_prompt = \"\"\"Solve the following task or problem. To solve the problem, we have made step-by-step Plan and \\\n",
    "retrieved corresponding Evidence to each Plan. Use them with caution since long evidence might \\\n",
    "contain irrelevant information.\n",
    "\n",
    "{plan}\n",
    "\n",
    "Now solve the question or task according to provided Evidence above. Respond with the answer\n",
    "directly with no extra words.\n",
    "\n",
    "Task: {task}\n",
    "Response:\"\"\"\n",
    "\n",
    "\n",
    "def solve(state: ReWOO):\n",
    "    plan = \"\"\n",
    "    for _plan, step_name, tool, tool_input in state[\"steps\"]:\n",
    "        _results = (state[\"results\"] or {}) if \"results\" in state else {}\n",
    "        for k, v in _results.items():\n",
    "            tool_input = tool_input.replace(k, v)\n",
    "            step_name = step_name.replace(k, v)\n",
    "        plan += f\"Plan: {_plan}\\n{step_name} = {tool}[{tool_input}]\"\n",
    "    prompt = solve_prompt.format(plan=plan, task=state[\"task\"])\n",
    "    result = llm.invoke(prompt)\n",
    "    return {\"result\": result.content, \"results\" : None}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "9、定义图形"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "def _route(state):\n",
    "    _step = _get_current_task(state)\n",
    "    if _step is None:\n",
    "        # We have executed all tasks\n",
    "        return \"solve\"\n",
    "    else:\n",
    "        # We are still executing tasks, loop back to the \"tool\" node\n",
    "        return \"tool\"\n",
    "\n",
    "from langgraph.graph import END, StateGraph, START\n",
    "\n",
    "graph = StateGraph(ReWOO)\n",
    "graph.add_node(\"plan\", get_plan)\n",
    "graph.add_node(\"tool\", tool_execution)\n",
    "graph.add_node(\"solve\", solve)\n",
    "graph.add_edge(\"plan\", \"tool\")\n",
    "graph.add_edge(\"solve\", END)\n",
    "graph.add_conditional_edges(\"tool\", _route)\n",
    "graph.add_edge(START, \"plan\")\n",
    "memory = MemorySaver()\n",
    "app = graph.compile(checkpointer=memory)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "10、执行"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [],\n",
       " 'task': '哈哈',\n",
       " 'plan_string': \"Task: A company is planning to launch a new product and wants to analyze the sentiment of potential customers on social media platforms to gauge possible reception. They need to collect, process, and analyze relevant data to make informed decisions.\\n\\nPlan: Identify the social media platforms that are most relevant for the target audience of the new product. #E1 = LLM[List the most popular social media platforms for demographic X]\\n\\nPlan: Design a data collection strategy to scrape or extract posts, comments, and reactions related to keywords associated with the new product. #E2 = LLM[Design a data collection strategy for extracting information from platforms identified in #E1 using keywords 'new product']\\n\\nPlan: Use a sentiment analysis tool to process the collected data to determine the overall sentiment (positive, negative, neutral) expressed by the users. #E3 = LLM[Recommend a sentiment analysis tool and explain how to apply it to the data collected in #E2]\\n\\nPlan: Analyze the sentiment data to identify key themes or issues that are frequently mentioned in relation to the new product. #E4 = LLM[How to analyze sentiment data to find common themes or issues]\\n\\nPlan: Summarize the findings and prepare a detailed report that includes data visualizations to present to the company's decision-makers. #E5 = LLM[Guide on how to summarize sentiment analysis findings and prepare a report with data visualizations]\",\n",
       " 'steps': [('Identify the social media platforms that are most relevant for the target audience of the new product. ',\n",
       "   '#E1',\n",
       "   'LLM',\n",
       "   'List the most popular social media platforms for demographic X'),\n",
       "  ('Design a data collection strategy to scrape or extract posts, comments, and reactions related to keywords associated with the new product. ',\n",
       "   '#E2',\n",
       "   'LLM',\n",
       "   \"Design a data collection strategy for extracting information from platforms identified in #E1 using keywords 'new product'\"),\n",
       "  ('Use a sentiment analysis tool to process the collected data to determine the overall sentiment (positive, negative, neutral) expressed by the users. ',\n",
       "   '#E3',\n",
       "   'LLM',\n",
       "   'Recommend a sentiment analysis tool and explain how to apply it to the data collected in #E2'),\n",
       "  ('Analyze the sentiment data to identify key themes or issues that are frequently mentioned in relation to the new product. ',\n",
       "   '#E4',\n",
       "   'LLM',\n",
       "   'How to analyze sentiment data to find common themes or issues'),\n",
       "  (\"Summarize the findings and prepare a detailed report that includes data visualizations to present to the company's decision-makers. \",\n",
       "   '#E5',\n",
       "   'LLM',\n",
       "   'Guide on how to summarize sentiment analysis findings and prepare a report with data visualizations')],\n",
       " 'results': None,\n",
       " 'result': \"Sorry, I can't assist with that.\"}"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "app.invoke({\"task\": \"哈哈\"}, config=config)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [],\n",
       " 'task': '今天天气怎么样',\n",
       " 'plan_string': 'Task: Calculate the volume of a cylinder given its height is 10 cm and its radius is 5 cm.\\n\\nPlan: Identify the formula for calculating the volume of a cylinder.\\n#E1 = LLM[What is the formula for calculating the volume of a cylinder?]\\n\\nPlan: Apply the given values (height = 10 cm and radius = 5 cm) to the formula to calculate the volume.\\n#E2 = LLM[Using the formula V = πr^2h, where r is the radius and h is the height, calculate the volume of the cylinder with r = 5 cm and h = 10 cm.]\\n\\nPlan: Convert the calculated volume into the appropriate unit and confirm the final answer.\\n#E3 = LLM[Convert the volume calculated in #E2 into cubic centimeters if not already and confirm the final volume of the cylinder.]',\n",
       " 'steps': [['Identify the formula for calculating the volume of a cylinder.',\n",
       "   '#E1',\n",
       "   'LLM',\n",
       "   'What is the formula for calculating the volume of a cylinder?'],\n",
       "  ['Apply the given values (height = 10 cm and radius = 5 cm) to the formula to calculate the volume.',\n",
       "   '#E2',\n",
       "   'LLM',\n",
       "   'Using the formula V = πr^2h, where r is the radius and h is the height, calculate the volume of the cylinder with r = 5 cm and h = 10 cm.'],\n",
       "  ['Convert the calculated volume into the appropriate unit and confirm the final answer.',\n",
       "   '#E3',\n",
       "   'LLM',\n",
       "   'Convert the volume calculated in #E2 into cubic centimeters if not already and confirm the final volume of the cylinder.']],\n",
       " 'results': {'#E1': \"content='The formula for calculating the volume of a cylinder is:\\\\n\\\\n\\\\\\\\[ V = \\\\\\\\pi r^2 h \\\\\\\\]\\\\n\\\\nwhere \\\\\\\\( V \\\\\\\\) is the volume, \\\\\\\\( r \\\\\\\\) is the radius of the circular base, \\\\\\\\( h \\\\\\\\) is the height of the cylinder, and \\\\\\\\( \\\\\\\\pi \\\\\\\\) (pi) is approximately 3.14159.' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 71, 'prompt_tokens': 19, 'total_tokens': 90, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': None, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': None}}, 'model_name': 'gpt-4o', 'system_fingerprint': 'fp_04751d0b65', 'finish_reason': 'stop', 'logprobs': None} id='run-b41f8f2f-b030-42b2-9a25-4fedc469ab06-0' usage_metadata={'input_tokens': 19, 'output_tokens': 71, 'total_tokens': 90, 'input_token_details': {}, 'output_token_details': {}}\",\n",
       "  '#E2': \"content='To calculate the volume of the cylinder using the formula \\\\\\\\( V = \\\\\\\\pi r^2 h \\\\\\\\), where \\\\\\\\( r \\\\\\\\) is the radius and \\\\\\\\( h \\\\\\\\) is the height, we can substitute the given values into the formula:\\\\n\\\\nGiven:\\\\n- \\\\\\\\( r = 5 \\\\\\\\) cm\\\\n- \\\\\\\\( h = 10 \\\\\\\\) cm\\\\n\\\\nSubstitute these values into the formula:\\\\n\\\\n\\\\\\\\[\\\\nV = \\\\\\\\pi \\\\\\\\times (5 \\\\\\\\text{ cm})^2 \\\\\\\\times 10 \\\\\\\\text{ cm}\\\\n\\\\\\\\]\\\\n\\\\nCalculate \\\\\\\\( (5 \\\\\\\\text{ cm})^2 \\\\\\\\):\\\\n\\\\n\\\\\\\\[\\\\n(5 \\\\\\\\text{ cm})^2 = 25 \\\\\\\\text{ cm}^2\\\\n\\\\\\\\]\\\\n\\\\nNow substitute back into the formula:\\\\n\\\\n\\\\\\\\[\\\\nV = \\\\\\\\pi \\\\\\\\times 25 \\\\\\\\text{ cm}^2 \\\\\\\\times 10 \\\\\\\\text{ cm}\\\\n\\\\\\\\]\\\\n\\\\n\\\\\\\\[\\\\nV = \\\\\\\\pi \\\\\\\\times 250 \\\\\\\\text{ cm}^3\\\\n\\\\\\\\]\\\\n\\\\nUsing the approximation \\\\\\\\( \\\\\\\\pi \\\\\\\\approx 3.14159 \\\\\\\\):\\\\n\\\\n\\\\\\\\[\\\\nV \\\\\\\\approx 3.14159 \\\\\\\\times 250 \\\\\\\\text{ cm}^3\\\\n\\\\\\\\]\\\\n\\\\n\\\\\\\\[\\\\nV \\\\\\\\approx 785.3975 \\\\\\\\text{ cm}^3\\\\n\\\\\\\\]\\\\n\\\\nTherefore, the volume of the cylinder is approximately \\\\\\\\( 785.40 \\\\\\\\text{ cm}^3 \\\\\\\\).' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 280, 'prompt_tokens': 48, 'total_tokens': 328, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': None, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': None}}, 'model_name': 'gpt-4o', 'system_fingerprint': 'fp_04751d0b65', 'finish_reason': 'stop', 'logprobs': None} id='run-6885084e-c251-4368-956a-29321f9c1169-0' usage_metadata={'input_tokens': 48, 'output_tokens': 280, 'total_tokens': 328, 'input_token_details': {}, 'output_token_details': {}}\",\n",
       "  '#E3': \"content='The volume of the cylinder has already been calculated in cubic centimeters. The volume was found using the formula:\\\\n\\\\n\\\\\\\\[ V = \\\\\\\\pi r^2 h \\\\\\\\]\\\\n\\\\nWith the given values:\\\\n- \\\\\\\\( r = 5 \\\\\\\\) cm\\\\n- \\\\\\\\( h = 10 \\\\\\\\) cm\\\\n\\\\nThe calculation was done as follows:\\\\n\\\\n1. Calculate \\\\\\\\( (5 \\\\\\\\text{ cm})^2 \\\\\\\\):\\\\n   \\\\\\\\[ (5 \\\\\\\\text{ cm})^2 = 25 \\\\\\\\text{ cm}^2 \\\\\\\\]\\\\n\\\\n2. Substitute into the formula:\\\\n   \\\\\\\\[ V = \\\\\\\\pi \\\\\\\\times 25 \\\\\\\\text{ cm}^2 \\\\\\\\times 10 \\\\\\\\text{ cm} \\\\\\\\]\\\\n\\\\n3. Simplify further:\\\\n   \\\\\\\\[ V = \\\\\\\\pi \\\\\\\\times 250 \\\\\\\\text{ cm}^3 \\\\\\\\]\\\\n\\\\n4. Use the approximation \\\\\\\\( \\\\\\\\pi \\\\\\\\approx 3.14159 \\\\\\\\):\\\\n   \\\\\\\\[ V \\\\\\\\approx 3.14159 \\\\\\\\times 250 \\\\\\\\text{ cm}^3 \\\\\\\\]\\\\n\\\\n5. Calculate the approximate volume:\\\\n   \\\\\\\\[ V \\\\\\\\approx 785.3975 \\\\\\\\text{ cm}^3 \\\\\\\\]\\\\n\\\\nRounding to two decimal places, the volume of the cylinder is approximately \\\\\\\\( 785.40 \\\\\\\\text{ cm}^3 \\\\\\\\).\\\\n\\\\nTherefore, the final volume of the cylinder is indeed \\\\\\\\( 785.40 \\\\\\\\text{ cm}^3 \\\\\\\\), and it is already expressed in cubic centimeters.' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 294, 'prompt_tokens': 539, 'total_tokens': 833, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': None, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': None}}, 'model_name': 'gpt-4o', 'system_fingerprint': 'fp_04751d0b65', 'finish_reason': 'stop', 'logprobs': None} id='run-73de4bfa-4f7b-40e5-9678-cbfdb1d6a0fb-0' usage_metadata={'input_tokens': 539, 'output_tokens': 294, 'total_tokens': 833, 'input_token_details': {}, 'output_token_details': {}}\"},\n",
       " 'result': '抱歉，我无法提供当前的天气信息。建议您查阅当地的天气预报或使用天气应用来获取最新信息。'}"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "app.get_state(config).values"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "11、查看agent graph。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "try:\n",
    "    display(Image(app.get_graph().draw_mermaid_png()))\n",
    "except Exception:\n",
    "    # This requires some extra dependencies and is optional\n",
    "    pass"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "fay312-2",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
